/*
Pyxis 2.0
Copyright 2010-2011 Thomas W. Holtquist

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

using System;
using System.Collections;
using System.Threading;

using Microsoft.SPOT;
using Microsoft.SPOT.Presentation;
using Microsoft.SPOT.Presentation.Media;
using Microsoft.SPOT.Touch;

using Skewworks.Pyxis.EXT;
using Skewworks.Pyxis.Kernel;

namespace Skewworks.Pyxis.GUI.Controls
{

    [Serializable]
    public class Treeview : Control
    {

        #region Variables

        private ArrayList _nodes = new ArrayList();

        private int _scrollY = 0;
        private int _mY = 0;
        private int _scrollX = 0;
        private int _mX = 0;
        private bool _bMoved = false;

        private int _aH = 0;
        private int _aW = 0;
        private int _totalH = 0;
        private int _totalW = 0;

        private TreeviewNode _selNode = null;

        #endregion

        #region Events

        public event OnNodeTap NodeTapped;
        public event OnNodeCollapsed NodeCollapsed;
        public event OnNodeExpanded NodeExpanded;

        /// <summary>
        /// Event for Taps
        /// </summary>
        /// <param name="sender"></param>
        protected virtual void OnNodeCollapsed(object sender, TreeviewNode node)
        {
            if (NodeCollapsed != null) NodeCollapsed(sender, node);
        }

        /// <summary>
        /// Event for Taps
        /// </summary>
        /// <param name="sender"></param>
        protected virtual void OnNodeExpanded(object sender, TreeviewNode node)
        {
            if (NodeExpanded != null) NodeExpanded(sender, node);
        }

        /// <summary>
        /// Event for Taps
        /// </summary>
        /// <param name="sender"></param>
        protected virtual void OnNodeTap(TreeviewNode node, point e)
        {
            if (NodeTapped != null) NodeTapped(node, e);
        }

        #endregion

        #region Constructors

        public Treeview(int x, int y, int width, int height)
        {
            _x = x;
            _y = y;
            _w = width;
            _h = height;
        }

        #endregion

        #region Touch Invokes

        public override void TouchDown(object sender, point e)
        {
            if (!_enabled || !_visible) return;

            _mY = e.Y;
            _mX = e.X;
            TreeviewNode node = NodeFromPoint(e);
            if (node != null) 
                node.PenDown = true;

            // Begin TapHold
            _ptTapHold = e;
            _eTapHold = TapState.THWaiting;
            Thread thHold = new Thread(TapHoldWaiter);
            thHold.Start();
        }

        public override void TouchUp(object sender, point e)
        {
            if (!_enabled || !_visible) return;

            // Check Tap Hold
            if (_eTapHold == TapState.THComplete)
            {
                _eTapHold = TapState.Normal;
                return;
            }
            _eTapHold = TapState.Normal;

            if (_bMoved)
            {
                _bMoved = false;
            }
            else
            {
                // Get Selected Node
                TreeviewNode node = NodeFromPoint(e);
                rect ecRect;
                if (node != null)
                {
                    ecRect = new rect(node.Bounds.X, node.Bounds.Y, 13, 20);
                    if (ecRect.contains(e) && node.PenDown)
                    {
                        node.Expanded = !node.Expanded;
                        if (node.Expanded)
                        {
                            OnNodeExpanded(this, node);
                        }
                        else
                        {
                            ResetChildrenBounds(node);
                            OnNodeCollapsed(this, node);
                        }

                        Render(true);
                    }
                    else
                    {
                        // Reset States
                        if (!node.Selected && node.PenDown)
                        {
                            for (int i = 0; i < _nodes.Count; i++) ResetNodesState((TreeviewNode)_nodes[i]);
                            node.Selected = true;
                            _selNode = node;
                            Render(true);
                        }
                        OnNodeTap(node, e);
                    }
                }

                // Reset Pen States
                for (int i = 0; i < _nodes.Count; i++) SetChildrenPen((TreeviewNode)_nodes[i]);
            }
        }

        public override void TouchMove(object sender, point e)
        {
            if (!_enabled || !_visible) return;

            bool bRender = false;

            _eTapHold = TapState.Normal;

            if (_totalH > _h)
            {
                int nScroll = _scrollY + (e.Y - _mY);
                if (nScroll > 0) nScroll = 0;
                if (nScroll < -_totalH + _aH - 2) nScroll = -_totalH + _aH - 2;
                _mY = e.Y;
                _bMoved = true;
                if (_scrollY != nScroll)
                {
                    _scrollY = nScroll;
                    bRender = true;
                }
            }

            if (_totalW > _w)
            {
                int nScroll = _scrollX + (e.X - _mX);
                if (nScroll > 0) nScroll = 0;
                if (nScroll < -_totalW + _aW - 2) nScroll = -_totalW + _aW - 2;
                _mX = e.X;
                _bMoved = true;
                if (_scrollX != nScroll)
                {
                    _scrollX = nScroll;
                    bRender = true;
                }
            }

            if (bRender) Render(true);
        }

        private void TapHoldWaiter()
        {
            Thread.Sleep(750);
            if (_eTapHold == TapState.Normal)
                return;

            // Update Selection
            TreeviewNode node = NodeFromPoint(_ptTapHold);
            rect ecRect;
            if (node != null)
            {
                ecRect = new rect(node.Bounds.X, node.Bounds.Y, 13, 20);
                if (ecRect.contains(_ptTapHold) && node.PenDown)
                {
                    node.Expanded = !node.Expanded;
                    if (node.Expanded)
                    {
                        OnNodeExpanded(this, node);
                    }
                    else
                    {
                        ResetChildrenBounds(node);
                        OnNodeCollapsed(this, node);
                    }

                    Render(true);
                }
                else
                {
                    // Reset States
                    if (!node.Selected && node.PenDown)
                    {
                        for (int i = 0; i < _nodes.Count; i++) 
                            ResetNodesState((TreeviewNode)_nodes[i]);
                        node.Selected = true;
                        _selNode = node;
                        Render(true);
                    }
                }

                // Reset Pen States
                for (int i = 0; i < _nodes.Count; i++) 
                    SetChildrenPen((TreeviewNode)_nodes[i]);
            }

            // Fire event
            OnTapHold(this, _ptTapHold);
        }

        #endregion

        #region  Properties

        public TreeviewNode SelectedNode
        {
            get { return _selNode; }
            set 
            { 
                _selNode = value;
                Render(true);
            }
        }

        public override int Height
        {
            get { return _h; }
            set
            {
                if (value < 30) value = 30;
                _h = value;
                if (_parent != null) _parent.Render();
            }
        }

        public int Length
        {
            get { return _nodes.Count; }
        }

        #endregion

        #region Public Methods

        public void AddNode(TreeviewNode node)
        {
            node.Container = this;
            _nodes.Add(node);
            Render(true);
        }

        public void ClearNodes()
        {
            _nodes.Clear();
            Render(true);
        }

        public TreeviewNode Node(int index)
        {
            return (TreeviewNode)_nodes[index];
        }

        public void RemoveNode(TreeviewNode node)
        {
            _nodes.Remove(node);
            Render(true);
        }

        public void RemoveNodeAt(int index)
        {
            _nodes.RemoveAt(index);
            Render(true);
        }

        #endregion

        #region GUI

        public override void Render(bool flush)
        {
            if (_parent == null || _parent.ScreenBuffer == null || !_visible || _suspend) return;

            // Set clipping region in case we're off parent somewhere (can happen w/ scroll)
            _parent.ScreenBuffer.SetClippingRectangle(_parent.Left, _parent.Top, _parent.Width, _parent.Height);

            int y = 4;
            int x = 4;
            int insert = 13;
            Bitmap exp = Resources.GetBitmap(Resources.BitmapResources.expand);
            Bitmap col = Resources.GetBitmap(Resources.BitmapResources.collapse);
            Font fnt = FontManager.Arial;
            TreeviewNode node;
            point e;
            int maxX = _w;
            int tW, tH;
            bool bResetScroll = false;

            // Draw the border & background
            _parent.ScreenBuffer.DrawRectangle(Colors.Black, 1, Left, Top, _w, _h, 0, 0, Colors.White, 0, 0, Colors.White, 0, 0, 256);

            // Draw the Items
            rect Region = rect.intersect(new rect(_parent.Left, _parent.Top, _parent.Width, _parent.Height), new rect(Left + 1, Top + 1, _w - 2, _h - 2));
            _parent.ScreenBuffer.SetClippingRectangle(Region.X, Region.Y, Region.Width, Region.Height);
            for (int i = 0; i < _nodes.Count; i++)
            {
                node = (TreeviewNode)_nodes[i];

                // Set bounds so we can check hits
                fnt.ComputeExtent(node.Text, out tW, out tH);
                node.Bounds = new rect(_scrollX + Left + x, _scrollY + Top + y, tW + 13, 20);
                if (maxX < tW + 13 + x) maxX = tW + 13 + x;

                // Draw Icon only if there are children
                if (node.Length > 0)
                {
                    _parent.ScreenBuffer.DrawImage(_scrollX + Left + x, _scrollY + Top + y + 2, (node.Expanded) ? col : exp, 0, 0, 9, 9);
                }

                // Draw the text
                _parent.ScreenBuffer.DrawText(node.Text, fnt, (node.Selected) ? Colors.Blue : Colors.Black, _scrollX + Left + x + 13, _scrollY + Top + y);
                y += 20;

                if (node.Expanded)
                {
                    e = RenderSubNodes(node, x + insert, y, insert);
                    y = e.Y;
                    if (e.X > maxX) maxX = e.X;
                }

            }
            _parent.ScreenBuffer.SetClippingRectangle(0, 0, _parent.ScreenBuffer.Width, _parent.ScreenBuffer.Height);

            // Scrolling
            _aH = _h;
            _aW = _w;
            _totalH = y;
            _totalW = maxX;
            if (maxX > _w)
            {
                _totalH += 10;
                _aH -= 10;
            }
            if (y > _h)
            {
                _totalW += 10;
                _aW -= 10;
            }

            if (y > _h)
            {
                _parent.ScreenBuffer.DrawRectangle(Colors.DarkGray, 1, Left + _w - 10, Top + 1, 9, _aH - 2, 0, 0, Colors.DarkGray, 0, 0, Colors.DarkGray, 0, 0, 256);

                float sH = (float)(_totalH - _aH);
                float sY = (sH == 0) ? 0 : -((float)_scrollY) / (float)(_totalH - _aH);
                int iSY = (int)((float)(_aH - 24) * sY);
                _parent.ScreenBuffer.DrawRectangle(Colors.LightGray, 1, Left + _w - 10, Top + 1 + iSY, 9, 20, 0, 0, Colors.LightGray, 0, 0, Colors.LightGray, 0, 0, 256);
            }
            else if (_scrollY < 0)
            {
                _scrollY = 0;
                bResetScroll = true;
            }

            if (maxX > _w)
            {
                _parent.ScreenBuffer.DrawRectangle(Colors.DarkGray, 1, Left + 1, Top + _h - 10, _aW - 2, 9, 0, 0, Colors.DarkGray, 0, 0, Colors.DarkGray, 0, 0, 256);

                float sW = (float)(_totalW - _aW);
                float sX = (sW == 0) ? 0 : -((float)_scrollX) / (float)(_totalW - _aW);
                int iSX = (int)((float)(_aW - 24) * sX);
                _parent.ScreenBuffer.DrawRectangle(Colors.LightGray, 1, Left + 1 + iSX, Top + _h - 10, 20, 9, 0, 0, Colors.LightGray, 0, 0, Colors.LightGray, 0, 0, 256);
            }
            else if (_scrollX < 0)
            {
                _scrollX = 0;
                bResetScroll = true;
            }

            if (bResetScroll)
            {
                Render(flush);
                return;
            }

            if (flush)
            {
                _parent.ScreenBuffer.Flush(Left, Top, _w, _h);
                _parent.ScreenBuffer.SetClippingRectangle(0, 0, AppearanceManager.ScreenWidth, AppearanceManager.ScreenHeight);
            }
        }

        private point RenderSubNodes(TreeviewNode node, int x, int y, int insert)
        {
            Bitmap exp = Resources.GetBitmap(Resources.BitmapResources.expand);
            Bitmap col = Resources.GetBitmap(Resources.BitmapResources.collapse);
            Font fnt = FontManager.Arial;
            TreeviewNode subnode;
            point e;
            int maxX = x;
            int tW, tH;

            // Draw the Items
            for (int i = 0; i < node.Length; i++)
            {
                subnode = node.Node(i);

                // Set Bounds so we can check hits
                fnt.ComputeExtent(subnode.Text, out tW, out tH);
                subnode.Bounds = new rect(_scrollX + Left + x, _scrollY + Top + y, tW + 13, 20);
                if (maxX < tW + 13 + x) maxX = tW + 13 + x;

                // Draw Icon only if there are children
                if (subnode.Length > 0)
                {
                    _parent.ScreenBuffer.DrawImage(_scrollX + Left + x, _scrollY + Top + y + 2, (subnode.Expanded) ? col : exp, 0, 0, 9, 9);
                }

                // Draw the text
                _parent.ScreenBuffer.DrawText(subnode.Text, fnt, (subnode.Selected) ? Colors.Blue : Colors.Black, _scrollX + Left + x + 13, _scrollY + Top + y);
                y += 20;

                if (subnode.Expanded)
                {
                    e = RenderSubNodes(subnode, x + insert, y, insert);
                    y = e.Y;
                    if (e.X > maxX) maxX = e.X;
                }

            }

            return new point(maxX, y);
        }

        #endregion

        #region Private Methods

        private TreeviewNode NodeFromPoint(point e)
        {
            TreeviewNode node;
            for (int i = 0; i < _nodes.Count; i++)
            {
                node = (TreeviewNode)_nodes[i];
                if (node.Bounds.contains(e)) return node;
                if (node.Length > 0)
                {
                    node = CheckNodePoint(node, e);
                    if (node != null) return node;
                }
            }
            return null;
        }

        private TreeviewNode CheckNodePoint(TreeviewNode node, point e)
        {
            TreeviewNode subnode;
            for (int i = 0; i < node.Length; i++)
            {
                subnode = node.Node(i);
                if (subnode.Bounds.contains(e)) return subnode;
                if (subnode.Length > 0)
                {
                    subnode = CheckNodePoint(subnode, e);
                    if (subnode != null) return subnode;
                }
            }
            return null;
        }

        private void SetChildrenPen(TreeviewNode node)
        {
            node.PenDown = false;
            TreeviewNode subnode;
            for (int i = 0; i < node.Length; i++)
            {
                subnode = node.Node(i);
                subnode.PenDown = false;
                if (subnode.Length > 0) SetChildrenPen(subnode);
            }
        }

        private void SetChildrenSelected(TreeviewNode node)
        {
            _selNode = null;
            node.Selected = false;
            TreeviewNode subnode;
            for (int i = 0; i < node.Length; i++)
            {
                subnode = node.Node(i);
                subnode.Selected = false;
                if (subnode.Length > 0) SetChildrenSelected(subnode);
            }
        }

        private void ResetNodesState(TreeviewNode topNode)
        {
            _selNode = null;
            topNode.Selected = false;
            topNode.PenDown = false;

            TreeviewNode subnode;
            for (int i = 0; i < topNode.Length; i++)
            {
                subnode = topNode.Node(i);
                subnode.Selected = false;
                subnode.PenDown = false;
                if (subnode.Length > 0) SetChildrenSelected(subnode);
            }
        }

        private void ResetChildrenBounds(TreeviewNode node)
        {
            TreeviewNode subnode;
            for (int i = 0; i < node.Length; i++)
            {
                subnode = node.Node(i);
                subnode.Bounds = new rect(0, 0, 0, 0);
                if (subnode.Length > 0) ResetChildrenBounds(subnode);
            }
        }

        #endregion

    }
}
